﻿using System;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.Windows.Controls.Ribbon;
using System.Collections.ObjectModel;
using System.Diagnostics;
using Mesuro.Graphing.Utilities;
using Mesuro.Graphing;
using System.ComponentModel;
using Mesuro.DataManagement;
using Mesuro.Graphing.Plots;
using System.Windows.Controls.Primitives;
using System.Xml;
using Mesuro.Common;
using System.Windows.Data;
using System.Collections.Generic;

namespace Mesuro.GUI {
	/// <summary>
	/// Interaction logic for MainWindow.xaml
	/// </summary>
	public partial class MainWindow : RibbonWindow {
		public static readonly double[] FontSizeArray = { 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 };
		public const string RES_ChannelGrid = "ChannelGrid";
		public const string RES_HelpPane = "HelpPane";
		public const string RES_FilterDesigner = "FilterDesigner";

		#region DP: Sheets
		public static readonly DependencyProperty SheetsProperty = DependencyProperty.RegisterAttached( "Sheets", typeof( ObservableCollection<Sheet> ), typeof( MainWindow ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, SheetsChangedStatic ) );
		public ObservableCollection<Sheet> Sheets {
			get { return (ObservableCollection<Sheet>)GetValue( SheetsProperty ); }
			set { SetValue( SheetsProperty, value ); }
		}
		public static ObservableCollection<Sheet> GetSheets( DependencyObject element ) { return (ObservableCollection<Sheet>)element.GetValue( SheetsProperty ); }
		public static void SetSheets( DependencyObject element, ObservableCollection<Sheet> value ) { element.SetValue( SheetsProperty, value ); }
		private static void SheetsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: CurrentSheet
		public static readonly DependencyProperty CurrentSheetProperty = DependencyProperty.RegisterAttached( "CurrentSheet", typeof( Sheet ), typeof( MainWindow ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, CurrentSheetChangedStatic ) );
		public Sheet CurrentSheet {
			get { return (Sheet)GetValue( CurrentSheetProperty ); }
			set { SetValue( CurrentSheetProperty, value ); }
		}
		public static Sheet GetCurrentSheet( DependencyObject element ) { return (Sheet)element.GetValue( CurrentSheetProperty ); }
		public static void SetCurrentSheet( DependencyObject element, Sheet value ) { element.SetValue( CurrentSheetProperty, value ); }
		private static void CurrentSheetChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) {
			Sheet el = sender as Sheet;
			if (el != null)
				el.RaiseEvent( new RoutedPropertyChangedEventArgs<Sheet>( (Sheet)e.OldValue, (Sheet)e.NewValue, CurrentSheetChangedEvent ) );
		}

		public static readonly RoutedEvent CurrentSheetChangedEvent = EventManager.RegisterRoutedEvent( "CurrentSheetChanged", RoutingStrategy.Bubble, typeof( RoutedPropertyChangedEventArgs<Sheet> ), typeof( MainWindow ) );
		public event RoutedPropertyChangedEventHandler<Sheet> CurrentSheetChanged {
			add { AddHandler( CurrentSheetChangedEvent, value ); }
			remove { RemoveHandler( CurrentSheetChangedEvent, value ); }
		}
		public static void AddCurrentSheetChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<Sheet> handler ) {
			if (d != null && d is Sheet)
				((Sheet)d).AddHandler( CurrentSheetChangedEvent, handler );
		}
		public static void RemoveCurrentSheetChangedHandler( DependencyObject d, RoutedPropertyChangedEventHandler<Sheet> handler ) {
			if (d != null && d is Sheet)
				((Sheet)d).RemoveHandler( CurrentSheetChangedEvent, handler );
		}

		#endregion

		#region DP: Experiments
		public static readonly DependencyProperty ExperimentsProperty = DependencyProperty.RegisterAttached( "Experiments", typeof( ExperimentHeader[] ), typeof( MainWindow ), new FrameworkPropertyMetadata( new ExperimentHeader[0], FrameworkPropertyMetadataOptions.None, ExperimentsChangedStatic ) );
		public ExperimentHeader[] Experiments {
			get { return (ExperimentHeader[])GetValue( ExperimentsProperty ); }
			set { SetValue( ExperimentsProperty, value ); }
		}
		public static ExperimentHeader[] GetExperiments( DependencyObject element ) { return (ExperimentHeader[])element.GetValue( ExperimentsProperty ); }
		public static void SetExperiments( DependencyObject element, ExperimentHeader[] value ) { element.SetValue( ExperimentsProperty, value ); }
		private static void ExperimentsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		#region DP: ActiveExperiment
		public static readonly DependencyProperty ActiveExperimentProperty = DependencyProperty.RegisterAttached( "ActiveExperiment", typeof( ExperimentHeader ), typeof( MainWindow ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, ActiveExperimentChangedStatic ) );
		public ExperimentHeader ActiveExperiment {
			get { return (ExperimentHeader)GetValue( ActiveExperimentProperty ); }
			set { SetValue( ActiveExperimentProperty, value ); }
		}
		public static ExperimentHeader GetActiveExperiment( DependencyObject element ) { return (ExperimentHeader)element.GetValue( ActiveExperimentProperty ); }
		public static void SetActiveExperiment( DependencyObject element, ExperimentHeader value ) { element.SetValue( ActiveExperimentProperty, value ); }
		private static void ActiveExperimentChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion
		
		#region DP: RecentlyUsedFonts
		public static readonly DependencyProperty RecentlyUsedFontsProperty = DependencyProperty.RegisterAttached( "RecentlyUsedFonts", typeof( ObservableCollection<FontFamily> ), typeof( MainWindow ), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.None, RecentlyUsedFontsChangedStatic ) );
		public ObservableCollection<FontFamily> RecentlyUsedFonts {
			get { return (ObservableCollection<FontFamily>)GetValue( RecentlyUsedFontsProperty ); }
			set { SetValue( RecentlyUsedFontsProperty, value ); }
		}
		public static ObservableCollection<FontFamily> GetRecentlyUsedFonts( DependencyObject element ) { return (ObservableCollection<FontFamily>)element.GetValue( RecentlyUsedFontsProperty ); }
		public static void SetRecentlyUsedFonts( DependencyObject element, ObservableCollection<FontFamily> value ) { element.SetValue( RecentlyUsedFontsProperty, value ); }
		private static void RecentlyUsedFontsChangedStatic( DependencyObject sender, DependencyPropertyChangedEventArgs e ) { }
		#endregion

		private Database _database = new Database();
		public Database Database {
			get { return _database; }
			private set { _database = value; }
		}

		static MainWindow() {
			//Force initialise Graph
			System.Runtime.CompilerServices.RuntimeHelpers.RunClassConstructor( typeof( Graph ).TypeHandle );
		}
		public MainWindow() {
#if DEBUG
			DefaultMutableSeries s1 = new DefaultMutableSeries( "theta", "sinTheta" ) { Name = "Sine Test" };
			for (double x = 0; x <= 2; x += 1 / 20d) {
				Common.Vector vec = new Common.Vector( 2 );
				vec[0] = x;
				vec[1] = Math.Sin( Math.PI * x );
				s1.AddDatum( vec );
			}
			DataSet.AddSeries( s1 );

			DefaultMutableSeries s2 = new DefaultMutableSeries( "rho_real", "rho_imag", "offset" ) { Name = "Matching Test" };
			int off = 0;
			//for (int off = -1; off <= 1; off += 1)
			for (double x = -1; x <= 1; x += 1 / 20d) {
				Common.Vector vec = new Common.Vector( 3 );
				vec[0] = off + 0.5 * Math.Cos( Math.PI * x ) - 0.2;
				vec[1] = off + 0.5 * Math.Sin( Math.PI * x ) + 0.1;
				vec[2] = off;
				s2.AddDatum( vec );
			}
			DataSet.AddSeries( s2 );
#endif
			Sheets = new ObservableCollection<Sheet>();
			RecentlyUsedFonts = new ObservableCollection<FontFamily>();

			InitializeComponent();

			catAxesType.ItemsSource = GraphingRegistry.GetAxesNames();
			GraphingRegistry.AxesTypeListChanged += ( x, y ) => catAxesType.ItemsSource = GraphingRegistry.GetAxesNames();

			Database.PropertyChanged += DatabasePropertyChanged;
			scrContent.AddHandler( TextBoxBase.SelectionChangedEvent, (RoutedEventHandler)rtbSelectionChanged, true );
			scrContent.AddHandler( Selector.SelectionChangedEvent, (SelectionChangedEventHandler)OnActiveSelectionChanged, true );
			CurrentSheet = NewSheet();
		}

#if DEBUG
		private Brush[] DEBUGBRUSHES = { Brushes.White, Brushes.Red, Brushes.Green, Brushes.Blue, Brushes.Yellow };
#endif
		public void NotifyUser( string message ) {
			MessageBox.Show( message, "Program message" );
		}

		public Sheet NewSheet( Sheet copy = null ) {
			Sheet sht;
			if (copy == null) {
				sht = new Sheet( "Sheet " + (Sheets.Count + 1) );
#if DEBUG
				((Sheet)sht).Background = DEBUGBRUSHES[Sheets.Count % DEBUGBRUSHES.Length];
#endif
			} else if (copy is ICloneable)
				sht = (Sheet)((ICloneable)copy).Clone();
			else
				throw new ArgumentException( "Cannot copy non-cloneable element" );

			Sheets.Add( sht );

			return sht;
		}

		public bool DeleteSheet( Sheet sheet, bool force = false ) {
			bool ret = false;
			int oldIdx = lstSheets.SelectedIndex;

			if (sheet == null) {
				NotifyUser( "Nothing selected!" );
				return false;
			}

			if (Sheet.GetIsEmpty( CurrentSheet ) || force)
				ret = Sheets.Remove( sheet );

			else {
				MessageBoxResult result = MessageBox.Show( "This will delete the current sheet and all its content.", "Are you sure?", MessageBoxButton.OKCancel, MessageBoxImage.Exclamation, MessageBoxResult.Cancel );

				if (result == MessageBoxResult.OK)
					ret = Sheets.Remove( sheet );
			}

			if (oldIdx > Sheets.Count - 1)
				oldIdx--;

			lstSheets.SelectedIndex = oldIdx;

			return ret;
		}

		public void SetRightPaneContent( TabItem content ) {
			if (!RightPane.Items.Contains( content ))
				RightPane.Items.Add( content );

			RightPane.SelectedItem = content;
			RightPane.IsExpanded = true;
		}

		private RibbonTab GetTabForSelectionType( Type selectionType ) {
			if (selectionType == typeof( Graph ))
				return tabGraph;
			else if (selectionType == typeof( Plot ))
				return tabPlot;
			return null;
		}

		#region Events & Commands
		private void thmSheetListResize_DragDelta( object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e ) {
			lstSheets.Width = Math.Max( 0, lstSheets.ActualWidth + e.HorizontalChange );
		}

		private void thmRightPaneResize_DragDelta( object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e ) {
			DependencyObject tmp = RightPane.SelectedContent as DependencyObject;

			while (!(tmp is FrameworkElement) && tmp != null)
				tmp = VisualTreeHelper.GetParent( tmp ) as DependencyObject;

			if (tmp == null) return;

			FrameworkElement content = tmp as FrameworkElement;
			content.Width = Math.Max( 3, content.ActualWidth - e.HorizontalChange );
		}

		private void DatabasePropertyChanged( object sender, PropertyChangedEventArgs e ) {
			if (e.PropertyName == Database.PROP_Connected && Database.Connected)
				catTableList.ItemsSource = Database.ListTables();
			if (Database.Connected)
				galSweepList.ItemsSource = Database.ListExperiments();
		}

		private void DeleteSheet_CanExecute( object sender, CanExecuteRoutedEventArgs e ) {
			e.CanExecute = (lstSheets != null) && lstSheets.SelectedIndex >= 0 && lstSheets.SelectedIndex < Sheets.Count;
		}
		private void DeleteSheet_Executed( object sender, ExecutedRoutedEventArgs e ) {
			DeleteSheet( (Sheet)lstSheets.SelectedValue );
		}

		private void cmdNewSheet_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CurrentSheet = NewSheet();
		}

		private void cmdToggleDatabase_Executed( object sender, ExecutedRoutedEventArgs e ) {
			if (Database.Connected)
				Database.Disconnect();

			else if (!Database.Connect())
				MessageBox.Show( "An error occurred whilst attempting to connect to the database:\r\n" + Database.LastError.Message, "MySQL Error!", MessageBoxButton.OK, MessageBoxImage.Error );
		}

		private void cmdFirstPage_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CurrentSheet = Sheets[0];
		}

		private void cmdPreviousPage_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CurrentSheet = Sheets[Math.Max( 0, Sheets.IndexOf( CurrentSheet ) - 1 )];
		}

		private void cmdNextPage_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CurrentSheet = Sheets[Math.Min( Sheets.Count - 1, Sheets.IndexOf( CurrentSheet ) + 1 )];
		}

		private void cmdLastPage_Executed( object sender, ExecutedRoutedEventArgs e ) {
			CurrentSheet = Sheets[Sheets.Count - 1];
		}

		private void btnObjectOrder_Click( object sender, RoutedEventArgs e ) {
			RibbonSplitButton btn = sender as RibbonSplitButton;
			RibbonMenuItem mnu = e.Source as RibbonMenuItem;
			if (btn != null && mnu != null) {
				string param = (String)((RibbonMenuItem)e.Source).CommandParameter;
				btn.CommandParameter = param;
				btn.LargeImageSource = (ImageSource)Resources["imgObjectOrder" + param + "Large"];
				btn.SmallImageSource = (ImageSource)Resources["imgObjectOrder" + param + "Small"];
				btn.Label = (string)mnu.Header;
			}
		}
		
		private void galAxesSets_SelectionChanged( object sender, RoutedPropertyChangedEventArgs<object> e ) {

			Sheet sheet = CurrentSheet as Sheet;
			if (sheet == null) return;

			Graph graph = sheet.SelectedItem as Graph;
			if (sheet == null) return;

			Plot plot = graph.SelectedItem as Plot;
			if (plot == null) return;

			MultiAxes.SetAxisSet( plot, catAxesSets.Items.IndexOf( galAxesSets.SelectedItem ) );
		}

		protected void OnActiveSelectionChanged( object sender, SelectionChangedEventArgs e ) {
			Sheet s = e.OriginalSource as Sheet;
			if (s != null) {
				//Switch to appropriate tab for current selection
				if (e.AddedItems != null && e.AddedItems.Count > 0 && s.SelectedItems.Count == e.AddedItems.Count) {
					Type t = CommonPropertyProxy.GetCommonBaseClass( e.AddedItems );
					ribbon.SelectedValue = GetTabForSelectionType( t ) ?? ribbon.SelectedValue;
				}
			}
		}

		private void rtbSelectionChanged( object sender, RoutedEventArgs e ) {
			RichTextBox rtb = e.OriginalSource as RichTextBox;
			if (rtb == null) {
				return;
			}
			btnEditBold.IsChecked = FontWeights.Bold.Equals( rtb.Selection.GetPropertyValue( TextElement.FontWeightProperty ) );
			btnEditUnderline.IsChecked = TextDecorations.Underline.Equals( rtb.Selection.GetPropertyValue( Inline.TextDecorationsProperty ) );
			btnEditItalic.IsChecked = FontStyles.Italic.Equals( rtb.Selection.GetPropertyValue( TextElement.FontStyleProperty ) );

			btnAlignLeft.IsChecked = TextAlignment.Left.Equals( rtb.Selection.GetPropertyValue( Paragraph.TextAlignmentProperty ) );
			btnAlignCenter.IsChecked = TextAlignment.Center.Equals( rtb.Selection.GetPropertyValue( Paragraph.TextAlignmentProperty ) );
			btnAlignRight.IsChecked = TextAlignment.Right.Equals( rtb.Selection.GetPropertyValue( Paragraph.TextAlignmentProperty ) );
			btnAlignJustify.IsChecked = TextAlignment.Justify.Equals( rtb.Selection.GetPropertyValue( Paragraph.TextAlignmentProperty ) );
			
			galFontFamilies.SelectedItem = rtb.Selection.GetPropertyValue( TextElement.FontFamilyProperty );
			cboFontSizes.Text = rtb.Selection.GetPropertyValue( TextElement.FontSizeProperty ).ToString();
		}

		private void galFontFamilies_SelectionChanged( object sender, RoutedPropertyChangedEventArgs<object> e ) {
			RichTextBox rtb = CurrentSheet.SelectedItem as RichTextBox;
			if (rtb == null) return;
			rtb.Selection.ApplyPropertyValue( TextElement.FontFamilyProperty, e.NewValue );
		}

		private void galFontSizes_SelectionChanged( object sender, RoutedPropertyChangedEventArgs<object> e ) {
			RichTextBox rtb = CurrentSheet.SelectedItem as RichTextBox;
			if (rtb == null) return;
			rtb.Selection.ApplyPropertyValue( TextElement.FontSizeProperty, e.NewValue );
		}

		private void cboFontSizes_TextInput( object sender, TextCompositionEventArgs e ) {
			RichTextBox rtb = CurrentSheet.SelectedItem as RichTextBox;
			if (rtb == null) return;
			double size;
			if (!double.TryParse( e.ControlText.ToString(), out size ))
				System.Console.Beep();
			rtb.Selection.ApplyPropertyValue( TextElement.FontSizeProperty, size );
		}

		private void cmdShowSettings_Executed( object sender, ExecutedRoutedEventArgs e ) {
			OptionsDialog dlg = new OptionsDialog();
			dlg.Database = Database;
			dlg.ShowDialog();
			bool? retVal = dlg.DialogResult;
			Database = dlg.Database;
		}

		private void cmdSave_Executed( object sender, ExecutedRoutedEventArgs e ) {
			XmlTextWriter file = new XmlTextWriter( "C:\\Users\\Richard\\Desktop\\TextXML.xml", Encoding.UTF8 );
			file.Formatting = Formatting.Indented;
			foreach (Sheet s in Sheets) {
				XmlDocument doc = new XmlDocument();
				doc.AppendChild( s.XMLSerialise( doc ) );
				doc.WriteContentTo( file );
			}
			file.Close();
		}

		private void cmdShowChannels_Executed( object sender, ExecutedRoutedEventArgs e ) {
			object cg = Resources[RES_ChannelGrid];

			if (!RightPane.Items.Contains( cg ))
				RightPane.Items.Add( cg );

			RightPane.SelectedItem = cg;
			RightPane.IsExpanded = true;
		}

		private void OnRightPaneSelectionChanged( object sender, SelectionChangedEventArgs e ) {
			tglHelpPane.IsChecked = (RightPane.SelectedItem == Resources[RES_HelpPane]);
		}

		private void cmdToggleHelp_Executed( object sender, ExecutedRoutedEventArgs e ) {
			TabItem help = (TabItem)Resources[RES_HelpPane];
			if (tglHelpPane.IsChecked ?? false) {
				SetRightPaneContent( help );
			} else
				RightPane.Items.Remove( help );
		}

		private void cmdAddSeries_Executed( object sender, ExecutedRoutedEventArgs e ) {
			if (e.Parameter is ISeries)
				DataSet.AddSeries( (ISeries)e.Parameter );

			if (e.Parameter is ExperimentHeader) {
				ExperimentHeader eh = (ExperimentHeader)e.Parameter;
				Experiment exp = new Experiment( eh );
				DataSet.AddSeries( exp.Series );
			}
		}

		private void tglFilter_Click( object sender, RoutedEventArgs e ) {
			SetRightPaneContent( (TabItem)Resources[RES_FilterDesigner] );
			Graph g = CurrentSheet.SelectedValue as Graph;
			if (g != null) {
				Plot p = g.SelectedPlot;
				if (p != null) {
					FilterBuilder f = FilterDesigner.GetFilterBuilder( p );
					if (f == null)
						FilterDesigner.SetFilterBuilder( p, f = new FilterBuilder() );
					if (f.List.Count == 0)
						f.List.Add( new FilterBuilderElement() );
				}
			}
		}

		private void galPlotSplit_SelectionChanged( object sender, RoutedPropertyChangedEventArgs<object> e ) {
			Graph graph = CurrentSheet.SelectedItem as Graph;
			if (graph == null) return;

			Plot plot = graph.SelectedPlot;
			if (plot == null) return;

			IChannelRef splitChannel = e.NewValue as IChannelRef;
			int splitIdx = DataSet.GetSeries( splitChannel ).Channels.IndexOf( splitChannel.Channel );

			SplitPlot split = plot as SplitPlot;

			if (split != null) {
				graph.RemovePlot( split.SplitPlotGroup );
			}

			graph.RemovePlot( plot );
			ISeries series = DataSet.GetSeries( splitChannel );

			SplitPlotGroup spg = new SplitPlotGroup( plot, splitChannel, series.Select( ( vec ) => vec[splitIdx] ).Distinct() );
			graph.AddPlot( spg );
		}

		private void cmdNewChannel_Executed( object sender, ExecutedRoutedEventArgs e ) {
			popEquationEditor.IsOpen = true;
		}

		private void thmEqResize_DragDelta( object sender, DragDeltaEventArgs e ) {
			eqEditor.Width = Math.Max( eqEditor.MinWidth, eqEditor.ActualWidth + e.HorizontalChange );
			eqEditor.Height = Math.Max( eqEditor.MinHeight, eqEditor.ActualHeight + e.VerticalChange );
		}

		private void btnEqClose_Click( object sender, RoutedEventArgs e ) {
			popEquationEditor.IsOpen = false;
		}

		private void cmdImport_Executed( object sender, ExecutedRoutedEventArgs e ) {
			Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
			dlg.Title = "Import File";
			dlg.Filter = "SQL Server Database File (*.mdf)|*.mdf";
			if (dlg.ShowDialog() ?? false) {
				//TODO: Please Wait Dialog
				ExperimentHeader exp = DataExtractor.Parse( new System.IO.FileInfo( dlg.FileName ) );
				Database.InsertExperiment( exp );
			}

		}

		#endregion

	}
}
